package collectors.calendar.controllers;

import static play.libs.Json.toJson;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import collectors.calendar.models.Event;
import collectors.calendar.models.GoogleToken;
import collectors.calendar.models.CalendarUser;
import play.libs.F.Function;
import play.libs.F.Option;
import play.libs.F.Promise;
import play.libs.Json;
import play.libs.WS.Response;
import play.mvc.Controller;
import play.mvc.Result;
import collectors.calendar.services.GCalendarWrapper;
import collectors.calendar.util.OAuthUtil;
import views.html.index;

public class Application extends Controller {
	/**
	 * Provides information about the SM
	 * 
	 * @return
	 */
	public static Result getAbout() {
		  String version = "Version = Hi! I'm CalendarYou, the Social Machine responsible for easying access to all your events.";
		  return ok (toJson(version));		  
	  }
	
	public static Result getUsers() {
		  try {
			  return ok (toJson(CalendarUser.all()));
		  } catch (Exception e){
			  return badRequest("Error getting user' list.");
		  }
	 }
	
	// TODO: implement
	public static Result getMetadata(Long id) {
		return badRequest("Endpoint not yet implemented!");
	}
	public static Result index() {
		return ok("[YOU] - it's the CalendarYou Social Machine!");
	}
	
	public static Result cleardb() {
		  CalendarUser.emptyBD();
		  String version = "DataBase empty!";
		  return ok (toJson(version));		  
	 }
	
	public static Result searchEvents(Long id, String query) {
		 Promise<List<Event>> eventsPromise = GCalendarWrapper.search(CalendarUser.find.byId(id), GCalendarWrapper.calendarApiAccess, query);
		 return async(eventsPromise.map(new Function<List<Event>, Result>() {
				@Override
				public Result apply(List<Event> events) throws Throwable {
					return ok(toJson(events));
				}
			}));
		 
		
	 }

	public static Result getEventDetails(Long youId, String eventId) {
		return badRequest("Endpoint not yet implemented!");
	}

	/**
	 * 1. Token Request (response_type=code)  <========[do] link()
	 * 2. User login and consent
	 * 3. Receive code
	 * 4. Exchange Code For Token
	 * 5. Token response
	 * 
	 * see https://developers.google.com/accounts/docs/OAuth2#webserver
	 * 
	 */
	public static Result link(long youId) {
		if(youId == 0) {
			return badRequest("YOU-ID not informed - check if the user was successfully created before linking to CalendarYou");
		} else {
			// redirect to google auth page
			return redirect(OAuthUtil.getGoogleTokenRequestUrl(youId, GCalendarWrapper.calendarApiAccess));
		}
	}
	
	/**
	 * Called back from Google Calendar after Token Request and user consent.
	 * It performs the exchange code for token.
	 *
	 * 1. Token Request (response_type=code)
	 * 2. User login and consent
	 * 3. Receive code            [call]========>  linkCallback()
	 * 4. Exchange Code For Token <==========[do]  linkCallback()
	 * 5. Token response
	 * 
	 * see https://developers.google.com/accounts/docs/OAuth2#webserver
	 * 
	 * @param code
	 * @param state
	 * @param error
	 * @return
	 */
	public static Result linkCallback(Option<String> code, Option<String> state,
			Option<String> error) {

		if (error.isDefined()) {
			return badRequest(error.get());
		} else if (state.isDefined()) {
			String[] stateSplited = state.get().split("\\$\\$");
			Long youId = Long.parseLong(stateSplited[0]);
			if (code.isDefined()) {
				return exchangeCodeForToken(youId, code.get());
			} else {
				return badRequest("Access Code not returned - check the token request url (response_type=code)");
			}
		} else {
			return badRequest("YOU-ID not returned - check 'state' parameter");
		}
	}
	
	/**
	 * 1. Token Request (response_type=code)  
	 * 2. User login and consent
	 * 3. Receive code
	 * 4. Exchange Code For Token <======== exchangeCodeForToken()
	 * 5. Token response          ========> exchangeCodeForToken()
	 * 
	 * see https://developers.google.com/accounts/docs/OAuth2#webserver
	 * 
	 */
	public static Result exchangeCodeForToken(final long youId, String authcode) {
		// 4. Exchange Code For Token
		//    store token response, associating to the user
		Promise<CalendarUser> userPromise = GCalendarWrapper.exchangeTokenAndStore(youId, authcode, GCalendarWrapper.calendarApiAccess);
		return async(userPromise.map(new Function<CalendarUser, Result>() {
			@Override
			public Result apply(CalendarUser a) throws Throwable {
				// se na chamada do link foi passado um callback
				return redirect(controllers.routes.Calendaryou.linkCallback(youId, true));
			}
		}));
	}
	
	public static Result createUser() {
		CalendarUser user = CalendarUser.create();
		return ok(Json.toJson(user));
	}
	
	public static Result unlink(long youId) {
//		session().clear();
		final CalendarUser user = CalendarUser.find.byId(youId);
		Set<GoogleToken> toUnlink = user.linkedAccounts;
		List<Promise<? extends Response>> unlinkPromises = new ArrayList<Promise<? extends Response>>();
		
		for (GoogleToken linkData : toUnlink) {
			Promise<Response> promise = linkData.unlink();
			unlinkPromises.add(promise);
		}
		Promise<List<Response>> combinedPromise = Promise.sequence(unlinkPromises);
		
		return async(combinedPromise.map(new Function<List<Response>, Result>() {
			@Override
			public Result apply(List<Response> arg0) throws Throwable {
				boolean ok = true;
				List<String> errors = new ArrayList<String>();
				
				for (Response r : arg0) {
					if (r.getStatus() != 200) {
						ok = false;
						errors.add(r.getBody());
					}
				}
				user.clearLinks(); // limpa os tokens
				
				if (ok) {
					return ok("accounts successfuly unlinked");
				} else {
					return badRequest("error revoking tokens: "
							+ errors.toString());
				}
			}
		}));
	}

}
